﻿using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;

namespace Jack.SequentialGuid
{
	/// <summary>
	/// 有序GUID扩展
	/// </summary>
	public static class SequentialGuidExtensions
	{
#if NETFRAMEWORK || NETSTANDARD2_0

/* 项目“Jack.SequentialGuid (net462)”的未合并的更改
在此之前:
	// Was added in .NET Standard 2.1 and later so we only need to provide it for .NET Framework
	private static readonly DateTime UnixEpoch =
		new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
在此之后:
		// Was added in .NET Standard 2.1 and later so we only need to provide it for .NET Framework
		private static readonly DateTime UnixEpoch =
			new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
*/
		// Was added in .NET Standard 2.1 and later so we only need to provide it for .NET Framework
		private static readonly DateTime UnixEpoch =
			new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
#endif
		//See: https://www.sqlbi.com/blog/alberto/2007/08/31/how-are-guids-sorted-by-sql-server/
		//See: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/comparing-guid-and-uniqueidentifier-values
		private static readonly int[] GuidIndex = { 13, 12, 11, 10, 15, 14, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };
		private static readonly int[] SqlGuidIndex = { 12, 13, 14, 15, 10, 11, 8, 9, 7, 6, 3, 2, 1, 0, 5, 4 };

		private static DateTime ToDateTime(this long ticks) =>
			new DateTime(ticks, DateTimeKind.Utc);

		/// <summary>
		/// guid转时间
		/// </summary>
		/// <param name="guid">有序guid</param>
		/// <returns>返回有序guid对应的时间</returns>
#pragma warning disable CA1720 // 标识符包含类型名称
		public static DateTime ToDateTime(this Guid guid)
#pragma warning restore CA1720 // 标识符包含类型名称
		{
			var ticks = guid.ToTicks();
			if (ticks.IsDateTime()) return ticks.ToDateTime();

			//Try conversion through sql guid
			ticks = new SqlGuid(guid).ToGuid().ToTicks();
			return ticks.IsDateTime()
				? ticks.ToDateTime()
				: default(DateTime);
		}

		/// <summary>
		/// Sql Guid 转时间
		/// </summary>
		/// <param name="sqlGuid"></param>
		/// <returns></returns>
		public static DateTime ToDateTime(this SqlGuid sqlGuid) => sqlGuid.ToGuid().ToDateTime();

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sqlGuid"></param>
		/// <returns></returns>
		public static Guid ToGuid(this SqlGuid sqlGuid)
		{
			var bytes = sqlGuid.ToByteArray()
#if NET6_0_OR_GREATER
				!
#endif
			;
			return new Guid(GuidIndex.Select(i => bytes[i]).ToArray());
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="guid"></param>
		/// <returns></returns>
#pragma warning disable CA1720 // 标识符包含类型名称
		public static SqlGuid ToSqlGuid(this Guid guid)
#pragma warning restore CA1720 // 标识符包含类型名称
		{
			var bytes = guid.ToByteArray();
			return new SqlGuid(SqlGuidIndex.Select(i => bytes[i]).ToArray());
		}

		internal static bool IsDateTime(this long ticks) =>
			ticks <= DateTime.UtcNow.Ticks &&
			ticks >=
#if NETFRAMEWORK || NETSTANDARD2_0
					UnixEpoch
#else
					DateTime.UnixEpoch
#endif
							.Ticks;

		private static long ToTicks(this Guid guid)
		{
			var bytes = guid.ToByteArray();
			return
				((long)bytes[3] << 56) +
				((long)bytes[2] << 48) +
				((long)bytes[1] << 40) +
				((long)bytes[0] << 32) +
				((long)bytes[5] << 24) +
				(bytes[4] << 16) +
				(bytes[7] << 8) +
				bytes[6];
		}
	}
}
